home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 0.9.1.3 stable / flock-0.9.1.3.en-US.win32.exe / flock / components / flockOpmlService.js < prev    next >
Text File  |  2007-10-12  |  15KB  |  525 lines

  1. // vim: tabstop=2 softtabstop=2 shiftwidth=2 expandtab
  2. //
  3. // BEGIN FLOCK GPL
  4. // 
  5. // Copyright Flock Inc. 2005-2007
  6. // http://flock.com
  7. // 
  8. // This file may be used under the terms of of the
  9. // GNU General Public License Version 2 or later (the "GPL"),
  10. // http://www.gnu.org/licenses/gpl.html
  11. // 
  12. // Software distributed under the License is distributed on an "AS IS" basis,
  13. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. // for the specific language governing rights and limitations under the
  15. // License.
  16. // 
  17. // END FLOCK GPL
  18. //
  19.  
  20. const OPML_CONTRACTID = '@flock.com/opml-service;1';
  21. const OPML_CLASSID    = Components.ID('{a3948322-0bb8-413c-8c6d-b0959d6d5bad}');
  22. const OPML_CLASSNAME  = 'Flock OPML Service';
  23.  
  24. const MARK_READ_CONTRACTID = '@flock.com/opml-subscribe-listener-mark-read;1';
  25. const MARK_READ_CLASSID    = Components.ID('{4cb09bdd-cca3-4f8d-86b4-d4b315c42b45}');
  26. const MARK_READ_CLASSNAME  = 'Flock OPML Mark Read on Subscribe Listener';
  27.  
  28.  
  29. const Cc = Components.classes;
  30. const Ci = Components.interfaces;
  31. const Cr = Components.results;
  32.  
  33. const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n'
  34.  
  35. const MAX_OPML_DEPTH = 20;
  36.  
  37. /* from nspr's prio.h */
  38. const PR_RDONLY      = 0x01;
  39. const PR_WRONLY      = 0x02;
  40. const PR_RDWR        = 0x04;
  41. const PR_CREATE_FILE = 0x08;
  42. const PR_APPEND      = 0x10;
  43. const PR_TRUNCATE    = 0x20;
  44. const PR_SYNC        = 0x40;
  45. const PR_EXCL        = 0x80;
  46.  
  47.  
  48. function writeXML(xml, file) {
  49.   var xmlStr = XML_HEADER + xml.toXMLString();
  50.  
  51.   if (file.exists())
  52.     file.remove(false);
  53.  
  54.   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
  55.  
  56.   var ostream = Cc['@mozilla.org/network/file-output-stream;1']
  57.     .createInstance(Ci.nsIFileOutputStream);
  58.  
  59.   ostream.init(file, PR_WRONLY | PR_CREATE_FILE, 0644, 0);
  60.  
  61.   var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter']
  62.     .createInstance(Ci.nsIScriptableUnicodeConverter);
  63.   converter.charset = 'UTF-8';
  64.  
  65.   var convdata = converter.ConvertFromUnicode(xmlStr) + converter.Finish();
  66.  
  67.   ostream.write(convdata, convdata.length);
  68.  
  69.   ostream.flush();
  70.   ostream.close();
  71. }
  72.  
  73.  
  74. function OpmlRequest(url, folder, flatRoot, listener, ctxt, logger) {
  75.   this._url      = url;
  76.   this._folder   = folder;
  77.   this._flatRoot = flatRoot;
  78.   this._listener = listener;
  79.   this._ctxt     = ctxt;
  80.   this._logger   = logger;
  81.  
  82.   var ios = Cc['@mozilla.org/network/io-service;1']
  83.     .getService(Ci.nsIIOService);
  84.   this._channel = ios.newChannelFromURI(url, null, null);
  85. }
  86.  
  87. OpmlRequest.prototype = {
  88.   get: function OR_get() {
  89.     this._channel.asyncOpen(this, null);
  90.   },
  91.  
  92.   onStartRequest: function OR_onStartRequest(request, context) {
  93.     this._xml = '';
  94.   },
  95.   onStopRequest: function OR_onStopRequest(request, context, status) {
  96.     if (Components.isSuccessCode(status))
  97.       this._subscribe();
  98.     else if (this._listener)
  99.       this._listener.onLoadError(this._ctxt, null);
  100.   },
  101.   onDataAvailable: function OR_onDataAvailable(request, context, inputStream,
  102.                                                sourceOffset, count) {
  103.     var sstream = Cc['@mozilla.org/scriptableinputstream;1']
  104.       .createInstance(Ci.nsIScriptableInputStream);
  105.     sstream.init(inputStream);
  106.  
  107.     this._xml += sstream.read(count);
  108.   },
  109.  
  110.   _sendLoadError: function OR__sendLoadError(errorCode) {
  111.     if (this._listener) {
  112.       var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
  113.       error.errorCode = errorCode;
  114.       this._listener.onLoadError(this._ctxt, error);
  115.     }
  116.   },
  117.  
  118.   _subscribe: function OR__subscribe() {
  119.     var url = this._url.spec;
  120.     this._logger.info('got OPML data from ' + url);
  121.  
  122.     try {
  123.       var xmlData = new XML(this._xml.replace(/<\?xml[^?]*\?>/, ''));
  124.     }
  125.     catch (e) {
  126.       this._logger.error('Invalid XML at ' + url);
  127.       this._sendLoadError(Ci.flockIError.OPML_INVALID_XML);
  128.       return;
  129.     }
  130.  
  131.     var body = xmlData.body;
  132.  
  133.     if (xmlData.name() != 'opml' || !body) {
  134.       this._logger.error('No OPML data at ' + url);
  135.       this._sendLoadError(Ci.flockIError.OPML_NO_DATA);
  136.       return;
  137.     }
  138.  
  139.     var root;
  140.     if (!this._flatRoot) {
  141.       var title = xmlData.head.title;
  142.       if (title && title.toString().length > 0)
  143.         root = [xmlData.head.title.toString()];
  144.       else
  145.         root = ['Imported Feeds'];
  146.     } else {
  147.       root = [this._folder];
  148.     }
  149.  
  150.     this._feeds = [];
  151.     this._tooDeep = false;
  152.  
  153.     this._getFeeds(root, body);
  154.  
  155.     if (this._tooDeep) {
  156.       this._logger.error('Hierarchy at ' + url + ' is too deep');
  157.       this._sendLoadError(Ci.flockIError.OPML_TOO_DEEP);
  158.       return;
  159.     }
  160.  
  161.     if (this._feeds.length == 0) {
  162.       this._logger.error('No feeds in OPML at ' + url);
  163.       this._sendLoadError(Ci.flockIError.OPML_NO_FEEDS);
  164.       return;
  165.     }
  166.  
  167.     this._logger.info(this._feeds.length + ' feeds found in ' + url);
  168.     this._subscribeFeeds();
  169.   },
  170.  
  171.   _getFeeds: function OR__getFeeds(hierarchy, node) {
  172.     for each (var outline in node.outline) {
  173.       var xmlUrl = outline.@xmlUrl;
  174.       if (xmlUrl && xmlUrl.toString().length > 0 &&
  175.           !outline.hasComplexContent()) {
  176.         var feed = { hierarchy: hierarchy,
  177.                      url: xmlUrl.toString(),
  178.                      title: null
  179.                    };
  180.  
  181.         text = outline.@text;
  182.         if (!text || text.toString().length == 0)
  183.           text = outline.@title;
  184.  
  185.         if (text && text.toString().length > 0)
  186.           feed.title = text.toString();
  187.  
  188.         this._logger.info('Found feed ' + feed.url);
  189.         this._feeds.push(feed);
  190.       } else {
  191.         var title = outline.@title;
  192.         if (!title || title.toString().length == 0)
  193.           title = outline.@text;
  194.  
  195.         if (title && title.toString().length > 0) {
  196.           var newHierarchy = hierarchy.concat(title.toString());
  197.           if (newHierarchy.length > MAX_OPML_DEPTH) {
  198.             this._tooDeep = true;
  199.             return;
  200.           }
  201.  
  202.           this._logger.info('Found folder ' + title.toString());
  203.  
  204.           if (outline.hasComplexContent()) {
  205.             this._getFeeds(newHierarchy, outline);
  206.           } else {
  207.             var feed = { hierarchy: newHierarchy,
  208.                          url: null
  209.                        };
  210.             this._feeds.push(feed);
  211.           }
  212.  
  213.           if (this._tooDeep)
  214.             return;
  215.         }
  216.       }
  217.     }
  218.   },
  219.  
  220.   _subscribeFeeds: function OR__subscribeFeeds(folder, node) {
  221.     var fm = Cc['@flock.com/feed-manager;1']
  222.       .getService(Ci.flockIFeedManager);
  223.  
  224.     var ios = Cc['@mozilla.org/network/io-service;1']
  225.       .getService(Ci.nsIIOService);
  226.  
  227.     var subscriber = {
  228.       _feeds:    this._feeds,
  229.       
  230.       _listener: this._listener,
  231.       _ctxt:     this._ctxt,
  232.       _logger:   this._logger,
  233.       _url:      this._url,
  234.  
  235.       _index:    -1,
  236.  
  237.       get _feed() { return this._feeds[this._index]; },
  238.  
  239.       createFolderHierarchy: function(hierarchy) {
  240.         var destFolder = null;
  241.         for each (var folder in hierarchy) {
  242.           if (folder instanceof Ci.flockIFeedFolder) {
  243.             destFolder = folder;
  244.           } else {
  245.             var newFolder;
  246.             try {
  247.               newFolder = destFolder.addFolder(folder);
  248.               this._logger.info('Created folder: ' + folder);
  249.             }
  250.             catch (e) {
  251.               newFolder = destFolder.getChildFolder(folder);
  252.             }
  253.             destFolder = newFolder;
  254.           }
  255.         }
  256.         return destFolder;
  257.       },
  258.  
  259.       onGetFeedComplete: function(feed) {
  260.         if (this._listener)
  261.           this._listener.onGetFeed(feed, this._ctxt);
  262.  
  263.         var destFolder = this.createFolderHierarchy(this._feed.hierarchy);
  264.  
  265.         this._feed.hierarchy.length = 0;
  266.         this._feed.hierarchy.push(destFolder);
  267.  
  268.         this._logger.info('Subscribing ' + this._feed.url + ' to folder ' +
  269.                           destFolder.getTitle());
  270.         destFolder.subscribeFeed(feed);
  271.  
  272.         if (this._listener)
  273.           this._listener.onSubscribe(feed, this._ctxt,
  274.                                      this._index, this._feeds.length);
  275.  
  276.         this.processNextFeed();
  277.       },
  278.       onError: function(error) {
  279.         this._logger.warn('Error fetching ' + this._feed.url);
  280.  
  281.         if (this._listener)
  282.           this._listener.onError(this._feed.url, this._ctxt, error,
  283.                                  this._index, this._feeds.length);
  284.  
  285.         this.processNextFeed();
  286.       },
  287.  
  288.       processNextFeed: function() {
  289.         this._index++;
  290.  
  291.         while (this._index < this._feeds.length) {
  292.           if (this._feed.url == null) {
  293.             this.createFolderHierarchy(this._feed.hierarchy);
  294.             this._index++;
  295.             continue;
  296.           }
  297.  
  298.           var url = null;
  299.           try {
  300.             url = ios.newURI(this._feed.url, null, null);
  301.           }
  302.           catch (e) { }
  303.  
  304.           if (url) {
  305.             this._logger.info('Fetching feed ' + this._feed.url);
  306.             fm.getFeed(url, this);
  307.             return;
  308.           }
  309.  
  310.           if (this._listener) {
  311.             this._logger.warn('Bad feed URL: ' + this._feed.url);
  312.             var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
  313.             error.errorCode = error.OPML_BAD_FEED_URL;
  314.             this._listener.onError(this._feed.url, this._ctxt, error,
  315.                                    this._index, this._feeds.length);
  316.           }
  317.  
  318.           this._index++;
  319.         }
  320.  
  321.         this._logger.info('Finished OPML subscription of ' + this._url.spec);
  322.  
  323.         if (this._listener)
  324.           this._listener.onFinish(this._ctxt);
  325.       }
  326.     }
  327.  
  328.     subscriber.processNextFeed();
  329.   },
  330.  
  331.   QueryInterface: function OR_QueryInterface(iid) {
  332.     if (iid.equals(Ci.nsIStreamListener) ||
  333.         iid.equals(Ci.nsIRequestObserver)||
  334.         iid.equals(Ci.nsISupports))
  335.       return this;
  336.     throw Cr.NS_ERROR_NO_INTERFACE;
  337.   },
  338. }
  339.  
  340.  
  341. function OpmlService() {
  342.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  343.   this._logger.init('opmlservice');
  344.   this._logger.info('created');
  345. }
  346.  
  347. OpmlService.prototype = {
  348.   subscribe: function OPML_subscribe(url, folder, flatRoot, listener, ctxt) {
  349.     this._logger.info('subscribing OPML from ' + url);
  350.  
  351.     var req = new OpmlRequest(url, folder, flatRoot, listener, ctxt, this._logger);
  352.     req.get();
  353.   },
  354.  
  355.   export: function OPML_export(folder, title, file) {
  356.     var opml =
  357. <opml version="1.1">
  358. <head>
  359.   <title>{title}</title>
  360. </head>
  361. <body/>
  362. </opml>;
  363.  
  364.     this._exportFolder(folder, opml.body);
  365.  
  366.     writeXML(opml, file);
  367.   },
  368.  
  369.   _exportFolder: function OPML__exportFolder(folder, parentNode) {
  370.     var children = folder.getChildren();
  371.  
  372.     while (children && children.hasMoreElements()) {
  373.       var child = children.getNext();
  374.       var node = <outline/>;
  375.  
  376.       if (child instanceof Ci.flockIFeedFolder) {
  377.         node.@text = child.getTitle();
  378.         this._exportFolder(child, node);
  379.       } else {
  380.         node.@type = 'rss';
  381.         node.@text = child.getTitle();
  382.         node.@title = child.getTitle();
  383.         node.@xmlUrl = child.getURL().spec;
  384.       }
  385.  
  386.       parentNode.outline += node;
  387.     }
  388.   },
  389.  
  390.   getInterfaces: function OPML_getInterfaces(countRef) {
  391.     var interfaces = [Ci.flockIOpmlService, Ci.nsIClassInfo, Ci.nsISupports];
  392.     countRef.value = interfaces.length;
  393.     return interfaces;
  394.   },
  395.   getHelperForLanguage: function OPML_getHelperForLanguage(language) {
  396.     return null;
  397.   },
  398.   contractID: OPML_CONTRACTID,
  399.   classDescription: OPML_CLASSNAME,
  400.   classID: OPML_CLASSID,
  401.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  402.   flags: Ci.nsIClassInfo.SINGLETON,
  403.  
  404.   QueryInterface: function OPML_QueryInterface(iid) {
  405.     if (iid.equals(Ci.flockIOpmlService) ||
  406.         iid.equals(Ci.nsIClassInfo) ||
  407.         iid.equals(Ci.nsISupports))
  408.       return this;
  409.     throw Cr.NS_ERROR_NO_INTERFACE;
  410.   }
  411. }
  412.  
  413.  
  414. function MarkReadListener() {
  415. }
  416.  
  417. MarkReadListener.prototype = {
  418.   onSubscribe: function MARK_READ_onSubscribe(feed, ctxt, progress, progressMax) {
  419.   },
  420.   onFinish: function MARK_READ_onFinish(ctxt) {
  421.   },
  422.   onLoadError: function MARK_READ_onLoadError(ctxt, error) {
  423.   },
  424.   onError: function MARK_READ_onError(url, ctxt, error, progress, progressMax) {
  425.   },
  426.   onGetFeed: function MARK_READ_onGetFeed(feed, ctxt) {
  427.     feed.markRead();
  428.   },
  429.  
  430.   getInterfaces: function MARK_READ_getInterfaces(countRef) {
  431.     var interfaces = [Ci.flockIOpmlSubscribeListener, Ci.nsIClassInfo,
  432.                       Ci.nsISupports];
  433.     countRef.value = interfaces.length;
  434.     return interfaces;
  435.   },
  436.   getHelperForLanguage: function MARK_READ_getHelperForLanguage(language) {
  437.     return null;
  438.   },
  439.   contractID: MARK_READ_CONTRACTID,
  440.   classDescription: MARK_READ_CLASSNAME,
  441.   classID: MARK_READ_CLASSID,
  442.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  443.   flags: Ci.nsIClassInfo.SINGLETON,
  444.  
  445.   QueryInterface: function MARK_READ_QueryInterface(iid) {
  446.     if (iid.equals(Ci.flockIOpmlSubscribeListener) ||
  447.         iid.equals(Ci.nsIClassInfo) ||
  448.         iid.equals(Ci.nsISupports))
  449.       return this;
  450.     throw Cr.NS_ERROR_NO_INTERFACE;
  451.   }
  452. }
  453.  
  454.  
  455. function GenericComponentFactory(ctor) {
  456.   this._ctor = ctor;
  457. }
  458.  
  459. GenericComponentFactory.prototype = {
  460.  
  461.   _ctor: null,
  462.  
  463.   // nsIFactory
  464.   createInstance: function(outer, iid) {
  465.     if (outer != null)
  466.       throw Cr.NS_ERROR_NO_AGGREGATION;
  467.     return (new this._ctor()).QueryInterface(iid);
  468.   },
  469.  
  470.   // nsISupports
  471.   QueryInterface: function(iid) {
  472.     if (iid.equals(Ci.nsIFactory) ||
  473.         iid.equals(Ci.nsISupports))
  474.       return this;
  475.     throw Cr.NS_ERROR_NO_INTERFACE;
  476.   },
  477. };
  478.  
  479. var Module = {
  480.   QueryInterface: function(iid) {
  481.     if (iid.equals(Ci.nsIModule) ||
  482.         iid.equals(Ci.nsISupports))
  483.       return this;
  484.  
  485.     throw Cr.NS_ERROR_NO_INTERFACE;
  486.   },
  487.  
  488.   getClassObject: function(cm, cid, iid) {
  489.     if (!iid.equals(Ci.nsIFactory))
  490.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  491.  
  492.     if (cid.equals(OPML_CLASSID))
  493.       return new GenericComponentFactory(OpmlService)
  494.     if (cid.equals(MARK_READ_CLASSID))
  495.       return new GenericComponentFactory(MarkReadListener)
  496.  
  497.     throw Cr.NS_ERROR_NO_INTERFACE;
  498.   },
  499.  
  500.   registerSelf: function(cm, file, location, type) {
  501.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  502.     cr.registerFactoryLocation(OPML_CLASSID, OPML_CLASSNAME,
  503.                                OPML_CONTRACTID,
  504.                                file, location, type);
  505.     cr.registerFactoryLocation(MARK_READ_CLASSID, MARK_READ_CLASSNAME,
  506.                                MARK_READ_CONTRACTID,
  507.                                file, location, type);
  508.   },
  509.  
  510.   unregisterSelf: function(cm, location, type) {
  511.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  512.     cr.unregisterFactoryLocation(OPML_CLASSID, location);
  513.     cr.unregisterFactoryLocation(MARK_READ_CLASSID, location);
  514.   },
  515.  
  516.   canUnload: function(cm) {
  517.     return true;
  518.   },
  519. };
  520.  
  521. function NSGetModule(compMgr, fileSpec)
  522. {
  523.   return Module;
  524. }
  525.